package com.totoro.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import com.totoro.util.Page;

/**
 * 
 * @author zhangpeng
 * 
 * 这是实现了增删改查的超类Dao，其他Dao类继承该类即可复用各自的增删改查方法；
 *
 */
@SuppressWarnings("unchecked")
public class BaseDao<T> {
	
	private Class<T> entityClass;
	
	@Autowired
	private SessionFactory sessionFactory;
	
	@SuppressWarnings("rawtypes")
	public BaseDao() {
		Type genType = getClass().getGenericSuperclass();
		Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
		entityClass = (Class) params[0];
	}
	
	/**
	 * 根据ID获取对象
	 */
	public T findById(Serializable id) {
		return (T) getSession().get(entityClass, id);
	}
	
	/**
	 * 保存
	 */
	public void save(T entity) {
		getSession().save(entity);
		
	}

	/**
	 * 删除
	 */
	public void delete(T entity) {
		getSession().delete(entity);
	}

	/**
	 * 修改
	 */
	public void update(T entity) {
		getSession().update(entity);
	}

	/**
	 * 查询所有
	 */
	public List<T> findAll(String hql) {
		return getSession().createQuery(hql).list();
	}

	/**
	 * 获取session对象
	 */
	public Session getSession() {
		return sessionFactory.getCurrentSession();
	}
	
	/**
	 * 分页查询函数，使用hql.
	 *
	 * @param pageNo 页号,从1开始.
	 */
	public Page pagedQuery(String hql, int pageNo, int pageSize, Object... values) {
		/**
		 * Assert.hasText(String text); 
		 * Parameters:
		 *      text - the String to check;
		 * Note:Assert that the given String has valid text content; that is, 
		 *      it must not be null and must contain at least one non-whitespace character.
		 *      断言给出的字符串text有着合法的文本内容，也就是说参数必须不为空并且必须包含至少一个非空字符；
		 * eg:Assert.hasText(name, "'name' must not be empty");
		 */
		Assert.hasText(hql);
		/**
		 * Assert.isTrue(Boolean expresion,String message)
		 * Parameters:
		 *      expression - a boolean expression
         *      message - the exception message to use if the assertion fails
		 * Note:Assert a boolean expression, 
		 *      throwing IllegalArgumentException if the test result is false.
		 *      断言一个布尔表达式，如果测试结果为假，则会抛异常；
		 * eg:Assert.isTrue(i > 0, "The value must be greater than zero");
		 */
		Assert.isTrue(pageNo >= 1, "pageNo should start from 1");
		// Count查询
		String countQueryString = " select count (*) " + removeSelect(removeOrders(hql));
		System.out.println("=========01countQueryString: "+countQueryString);
		/**
		 * Hibernate执行select count (*)返回类型为Long类型；
		 * 这是Hibernate官方文档中的说明：
		 * a)COUNT returns Long.
         * b)MAX, MIN return the type of the state-field to which they are applied.
         * c)AVG returns Double.
         * d)SUM returns Long when applied to state-fields of integral types (other than BigInteger); Double
		 */
		System.out.println("=========values01:"+values);
		List<Object> countlist = createQuery(countQueryString, values).list();
		System.out.println("=========02countlist: "+countlist+"; values02:"+values);
		//返回第0个数的值；
		long totalCount = (Long) countlist.get(0);
		System.out.println("=========03totalCount: "+totalCount);
		if (totalCount < 1)
			return new Page();
		// 实际查询返回分页对象
		int startIndex = Page.getStartOfPage(pageNo, pageSize);
		System.out.println("=========04startIndex: "+startIndex);
		Query query = createQuery(hql, values);
		List<Object> list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();
		System.out.println("=========05list: "+list);
		return new Page(startIndex, totalCount, pageSize, list);
	}

	/**
	 * 创建Query对象. 对于需要first,max,fetchsize,cache,cacheRegion等诸多设置的函数,可以在返回Query后自行设置.
	 * 留意可以连续设置,如下：
	 * <pre>
	 * dao.getQuery(hql).setMaxResult(100).setCacheable(true).list();
	 * </pre>
	 * 调用方式如下：
	 * <pre>
	 *        dao.createQuery(hql)
	 *        dao.createQuery(hql,arg0);
	 *        dao.createQuery(hql,arg0,arg1);
	 *        dao.createQuery(hql,new Object[arg0,arg1,arg2])
	 * </pre>
	 *
	 * @param values 可变参数.
	 */
	public Query createQuery(String hql, Object... values) {
		Assert.hasText(hql);
		Query query = getSession().createQuery(hql);
		for (int i = 0; i < values.length; i++) {
			query.setParameter(i, values[i]);
		}
		return query;
	}
	/**
	 * 去除hql的select 子句，未考虑union的情况,用于pagedQuery.
	 * 获取from(包括from)以后的字符；
	 *
	 * @see #pagedQuery(String,int,int,Object[])
	 */
	private static String removeSelect(String hql) {
		Assert.hasText(hql);
		int beginPos = hql.toLowerCase().indexOf("from");
		Assert.isTrue(beginPos != -1, " hql : " + hql + " must has a keyword 'from'");
		/**
		 * a)String.substring(int beginIndex)
		 * Note:Returns a new string that is a substring of this string.
		 *      The substring begins with the character at the specified 
		 *      index and extends to the end of this string. 
		 * Examples:
		 *      "unhappy".substring(2) returns "happy"
		 *      "Harbison".substring(3) returns "bison"
		 *      "emptiness".substring(9) returns "" (an empty string)
		 *      
		 * b)String.substring(int beginIndex,int endIndex)
		 * Note:Returns a new string that is a substring of this string.
		 *      The substring begins at the specified beginIndex and extends
		 *      to the character at index endIndex - 1. Thus the length of the 
		 *      substring is endIndex-beginIndex;
		 * Examples:
		 *      "hamburger".substring(4, 8) returns "urge";
		 *      "smiles".substring(1, 5) returns "mile";
		 */
		return hql.substring(beginPos);
	}
	
	/**
	 * 去除hql的orderby 子句，用于pagedQuery.
	 *
	 * @see #pagedQuery(String,int,int,Object[])
	 */
	private static String removeOrders(String hql) {
		Assert.hasText(hql);
		Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(hql);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, "");
		}
		m.appendTail(sb);
		return sb.toString();
	}
}
